﻿/******************************************************
* author :  cwj
* email  :  chenwenji_360@live.com 
* history:  created by cwj 2015/7/25 12:20:31 
* clrversion :4.0.30319.18444
******************************************************/

using Machine.DataAccess.Common.ORM;
using Machine.DataAccess.Linq;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;

namespace Machine.DataAccess.Operation
{
    abstract class DataSetOperatorBase<TEntity> : IDataSetOperator<TEntity>
    {
        //protected static Dictionary<Type, CacheItem> _primaryKeyInfos = new Dictionary<Type, CacheItem>();
        protected Type EntityType { get; private set; }
        protected ProviderElement ProviderElement { get; private set; }
        protected abstract string TableBeforeChar { get; }
        protected abstract string TableAfterChar { get; }

        public DataSetOperatorBase(ProviderElement providerElement) 
        { 
            EntityType = typeof(TEntity);
            this.ProviderElement = providerElement;
        }

        public virtual TEntity GetByKey(object key)
        {
            var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);
            var cacheItem = schem.GetSchem(this.EntityType);
            string sql = string.Format("select * from {0} where {1} = @{1}", cacheItem.TableName, cacheItem.PrimaryKeyName);

            List<DbParameter> parameters = new List<DbParameter>();
            var parameter = schem.CreateDbParameter(string.Format("@{0}", cacheItem.PrimaryKeyName), key);
            parameters.Add(parameter);

            return DataResult.GetResult<TEntity>(new TranslateResult(sql, parameters), this.ProviderElement);
        }

        protected virtual object GetByKey(object key, Type type, DbSchemInfoBase schem)
        {
            var cacheItem = schem.GetSchem(type);//GetKeyName(typeof(TEntity));
            string sql = string.Format("select * from {0} where {1} = @{1}", cacheItem.TableName, cacheItem.PrimaryKeyName);

            List<DbParameter> parameters = new List<DbParameter>();
            var parameter = schem.CreateDbParameter(string.Format("@{0}", cacheItem.PrimaryKeyName), key);
            //CreateParameter("@id", key); //Config.Instance.ProviderFactory.CreateParameter();
            parameters.Add(parameter);

            var obj = Activator.CreateInstance(type);

            return DataResult.GetResult(new TranslateResult(sql, parameters), obj, this.ProviderElement);
        }

        public virtual bool DeleteByKey(object key)
        {
            var creator = Config.Instance.GetDbSchemCreator(this.ProviderElement);
            if (creator != null && creator.ProviderElement.IsCodeFirst == true) creator.CreateTable(typeof(TEntity));

            var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);
            var cacheItem = schem.GetSchem(this.EntityType);//GetKeyName(typeof(TEntity));
            var entity = GetByKey(key);
            if (entity == null) return false;
            string sql = string.Format("delete from {0} where {1} = @{1}", cacheItem.TableName, cacheItem.PrimaryKeyName);

            var parameters = new List<DbParameter>();// { new SqlParameter("id", key) };
            var parameter = schem.CreateDbParameter(string.Format("@{0}", cacheItem.PrimaryKeyName),key);
            parameters.Add(parameter);

            return DataResult.ExecuteNonQuery(new TranslateResult(sql, parameters),this.ProviderElement);
        }

        public virtual bool Delete(TEntity entity)
        {
            var creator = Config.Instance.GetDbSchemCreator(this.ProviderElement);
            if (creator != null && creator.ProviderElement.IsCodeFirst == true) creator.CreateTable(typeof(TEntity));

            var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);
            var cacheItem = schem.GetSchem(this.EntityType);

            var pkName = schem.GetPrimaryKey(this.EntityType.GetTableName());
            var pk = DynamicAssignment.Instance.GetDictionary(this.EntityType)[pkName];
            var pkValue = pk.GetValue(entity);

            return this.DeleteByKey(pkValue);
        }

        public virtual bool AddOrUpdate(TEntity entity)
        {
            var creator = Config.Instance.GetDbSchemCreator(this.ProviderElement);
            if (creator != null && creator.ProviderElement.IsCodeFirst == true) creator.CreateTable(typeof(TEntity));

            var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);//GetKeyName(typeof(TEntity));
            var properties = this.GetProperties(this.EntityType, entity, schem);
            return this.AddOrUpdateEntity(entity, properties, schem);
        }

        public virtual bool Add(TEntity entity)
        {
            var creator = Config.Instance.GetDbSchemCreator(this.ProviderElement);
            if (creator != null && creator.ProviderElement.IsCodeFirst ==true) creator.CreateTable(typeof(TEntity));

            var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);//GetKeyName(typeof(TEntity));
            var properties = this.GetProperties(this.EntityType,entity, schem);

            foreach (var item in properties)
            {
                if (item.Entity != null && item.Entity.GetType() != entity.GetType())
                {
                    if (!this.Contain(item.Entity, this.ProviderElement, schem))
                    {
                        this.AddOrUpdateEntity(item.Entity, this.GetProperties(item.Entity.GetType(), item.Entity, schem), schem);
                    }
                }
            }

            return AddEntity(entity, properties, schem);
        }

        public virtual bool Update(TEntity entity)
        {
            var creator = Config.Instance.GetDbSchemCreator(this.ProviderElement);
            if (creator != null && creator.ProviderElement.IsCodeFirst == true) creator.CreateTable(typeof(TEntity));

            var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);//GetKeyName(typeof(TEntity));
            var properties = this.GetProperties(entity.GetType(), entity, schem);

            //foreach (var item in properties)
            //{
            //    if (item.Entity != null && item.Entity.GetType() != this.EntityType)
            //    {
            //        UpdateEntity(item.Entity, this.GetProperties(item.Entity.GetType(), item.Entity, schem), schem);
            //    }
            //}

            var result = UpdateEntity(entity, properties, schem);

            foreach (var item in properties)
            {
                if (item.Entity == null)
                {
                    var type = item.PropertyInvoker.PropertyInfo.ReflectedType;
                    var relation = schem.GetTableRelationType(this.EntityType, type);
                    if (relation == TableRelationType.One2Many || relation == TableRelationType.One2Zero) continue;
                    var sql = string.Format("select {0} from {1}", item.CoumName, this.EntityType.GetTableName());
                    var id = DataResult.GetResult<Guid>(new TranslateResult(sql, new DbParameter[0]), this.ProviderElement);
                    if (id != default(Guid))
                    {
                        sql = string.Format("delete from {0} where {1} = @{1}", type.GetTableName(), item.PropertyInvoker.PropertyName);
                        var parameterName = string.Format("@{0}", item.PropertyInvoker.PropertyName);
                        DataResult.ExecuteNonQuery(new TranslateResult(sql, new DbParameter[] { schem.CreateDbParameter(parameterName, id) }),
                            this.ProviderElement);
                    }
                }
                else
                {
                    this.AddOrUpdateEntity(item.Entity, this.GetProperties(item.Entity.GetType(), item.Entity, schem), schem);
                }
            }

            return result;
        }

        public virtual bool AddEntity(object entity, CoumInfoCollection properties, DbSchemInfoBase schem)
        {
            StringBuilder sql = new StringBuilder();
            sql.Append("insert into ");
            sql.Append(this.TableBeforeChar);
            sql.Append(entity.GetType().GetTableName());
            sql.Append(this.TableAfterChar);
            sql.Append(properties.CreateInsertCoumText(false));
            sql.Append(" values ");
            sql.Append(properties.CreateInsertCoumText(true));
            var parameters = properties.CreateDbParameter();

            #region Old
            //foreach (var property in properties)
            //{
            //    if (schemItem.IsAuto && property.Key == schemItem.PrimaryKeyName) continue;
            //    var typeCode = Type.GetTypeCode(property.Value.PropertyInfo.PropertyType);
            //    if (typeCode == TypeCode.Object && property.Value.PropertyInfo.PropertyType != typeof(Guid)) continue;
            //    if (columIndex > 0) sql.Append(",");
            //    sql.Append(property.Key);
            //    columIndex++;
            //}
            //sql.Append(") values( ");
            //columIndex = 0;
            //foreach (var property in properties)
            //{
            //    if (schemItem.IsAuto && property.Key == schemItem.PrimaryKeyName) continue;
            //    var typeCode = Type.GetTypeCode(property.Value.PropertyInfo.PropertyType);
            //    if (typeCode == TypeCode.Object && property.Value.PropertyInfo.PropertyType != typeof(Guid)) continue;
            //    if (columIndex > 0) sql.Append(",");
            //    sql.Append(string.Format("@{0}", property.Key));
            //    columIndex++;
            //}
            //sql.Append(")");
            //var parameters = new List<DbParameter>();
            //foreach (var property in properties)
            //{
            //    var parameter = CreateParameter(property.Key, property.Value.GetValue(entity));
            //    parameters.Add(parameter);
            //}
            #endregion
            return DataResult.ExecuteNonQuery(new TranslateResult(sql.ToString(), parameters), this.ProviderElement);
        }
        public virtual bool UpdateEntity(object entity, CoumInfoCollection properties, DbSchemInfoBase schem)
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("update ");
            builder.Append(this.TableBeforeChar);
            builder.Append(entity.GetType().GetTableName());
            builder.Append(this.TableAfterChar);
            builder.Append(" ");
            builder.Append(properties.CreateUpdateCoumText());
            builder.Append(" ");
            builder.Append(" where ");
            builder.Append(properties.CreatePrimaryCoumText());
            var parameters = properties.CreateDbParameter();
            //StringBuilder builder = new StringBuilder();
            //builder.Append("update ");
            //builder.Append(schemItem.TableName);
            //List<DbParameter> parameters = new List<DbParameter>();
            //foreach (var property in properties)
            //{
            //    var oldValue = property.Value.GetValue(dbEntity);
            //    var newValue = property.Value.GetValue(entity);
            //    if (!oldValue.Equals(newValue))
            //    {
            //        if (parameters.Count == 0) builder.Append(" set ");
            //        else builder.Append(", ");
            //        builder.Append(string.Format("{0} = @{0} ", property.Key));
            //        parameters.Add(CreateParameter(property.Key, newValue));
            //    }
            //}
            //if (parameters.Count == 0) return false;

            //builder.Append(string.Format("where {0} = @{0}", schemItem.PrimaryKeyName));

            //if (parameters.FirstOrDefault(x => x.ParameterName == schemItem.PrimaryKeyName) == null)
            //    parameters.Add(CreateParameter(schemItem.PrimaryKeyName, properties[schemItem.PrimaryKeyName].GetValue(entity)));
            return DataResult.ExecuteNonQuery(new TranslateResult(builder.ToString(), parameters), this.ProviderElement);
        }
        public virtual bool AddOrUpdateEntity(object entity, CoumInfoCollection properties, DbSchemInfoBase schem)
        {
            var keyName = schem.GetPrimaryKey(entity.GetType().GetTableName());
            var key = properties.Where(x=>x.Entity != null).FirstOrDefault(x => x.Entity.Equals(entity) && x.CoumName.ToLower() == keyName);
            var dbEntity = this.GetByKey(key.PropertyInvoker.GetValue(entity), entity.GetType(), schem);
            var keyValue = dbEntity == null ? null : (object)key.PropertyInvoker.GetValue(dbEntity);
            var dbKey = key.PropertyInvoker.GetValue(entity);

            foreach (var item in properties)
            {
                if (item.Entity != null && !item.Entity.Equals(entity))
                {
                    AddOrUpdateEntity(item.Entity, this.GetProperties(item.Entity.GetType(), item.Entity, schem), schem);
                }
            }

            if (keyValue == null || !keyValue.Equals(dbKey)) return AddEntity(entity, properties, schem);
            else return UpdateEntity(entity, properties, schem);
        }

        private bool Contain(object obj,ProviderElement providerElement, DbSchemInfoBase schem)
        {
            var pkName = schem.GetPrimaryKey(obj.GetType().GetTableName());
            var pkValue = DynamicAssignment.Instance.GetDictionary(obj.GetType())[pkName].GetValue(obj);
            //var parameterNane = string.Format("@{0}", pkName);
            string sql = string.Format("select {0} from {1} where {0} = @{0}", pkName, obj.GetType().GetTableName());

            var id = DataResult.GetResult<object>(new TranslateResult(sql,
                new DbParameter[] { schem.CreateDbParameter(pkName, pkValue) }),
                providerElement);
            if (id == null) return false;
            return id.Equals(pkValue);
        }

        //public virtual bool DeleteEntity(object key, Type type, DbSchemInfoBase schem)
        //{
        //    var fk = schem.GetAllRelationFK(type);
        //    throw new NotImplementedException();
        //}

        //[Obsolete]
        //private DbParameter CreateParameter(string name, object value)
        //{
        //    var parameter = DbProviderFactories.GetFactory(this.ProviderElement.Provider).CreateParameter();//Config.Instance.ProviderFactory.CreateParameter();
        //    parameter.ParameterName = name;
        //    parameter.Value = value;
        //    return parameter;
        //}

        public CoumInfoCollection GetProperties(Type mainType, object entity, DbSchemInfoBase schem)
        {
            //var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);
            CoumInfoCollection list = new CoumInfoCollection(schem,mainType);
            var properties = DynamicAssignment.Instance.GetDictionary(mainType);
            foreach (var item in properties)
            {
                var propertyInfo = item.Value.PropertyInfo;
                var typeCode = Type.GetTypeCode(propertyInfo.PropertyType);
                if (typeCode != TypeCode.Object) list.Add(new CoumInfo() { CoumName = propertyInfo.Name, PropertyInvoker = item.Value, Entity = entity });
                if (typeCode == TypeCode.Object && (propertyInfo.PropertyType == typeof(Guid) || propertyInfo.PropertyType == typeof(byte[])))
                {
                    list.Add(new CoumInfo() { CoumName = propertyInfo.Name, PropertyInvoker = item.Value, Entity = entity });
                }
                if (typeCode == TypeCode.Object && propertyInfo.PropertyType.IsGenericType == false && propertyInfo.PropertyType != typeof(Guid))
                {
                    var outterKeyTemp = schem.GetForeignKey(mainType.GetTableName());
                    if (outterKeyTemp.Count() > 0)
                    {
                        var outterKey = outterKeyTemp.FirstOrDefault(x => x.Table.ToLower() == propertyInfo.PropertyType.GetTableName());
                        if (outterKey != null)
                        {
                            var pk = schem.GetPrimaryKey(propertyInfo.PropertyType.GetTableName());
                            list.Add(new CoumInfo()
                             {
                                 CoumName = outterKey.From,
                                 PropertyInvoker = DynamicAssignment.Instance.GetDictionary(propertyInfo.PropertyType)[pk],
                                 Entity = item.Value.GetValue(entity)
                             });
                        }
                    }
                }
            }
            return list;
        }
    }

    class CoumInfo
    {
        public IPropertyInvoker PropertyInvoker { get; set; }
        public string CoumName { get; set; }
        public object Entity { get; set; }

        public DbParameter CreateDbParameter(DbSchemInfoBase schem)
        {
            var value = Entity == null ? null : PropertyInvoker.GetValue(Entity);
            value = value == null ? "" : value;
            return schem.CreateDbParameter(string.Format("@{0}", CoumName), value);
        }

        public string CreateInsertCoumText(bool isParameter)
        {
            if (!isParameter) return CoumName;
            return string.Format("@{0}", CoumName);
        }

        public string CreateUpdateCoumText()
        {
            return string.Format("{0} = @{0}", this.CoumName);
        }

        public string CreateWhereCoumText()
        {
            return string.Format("{0} = @{0}", this.CoumName);
        }
    }

    class CoumInfoCollection:List<CoumInfo>
    {
        DbSchemInfoBase schem;
        Type type;
        public CoumInfoCollection(DbSchemInfoBase schem,Type type)
        {
            this.schem = schem;
            this.type = type;
        }

        public IEnumerable<DbParameter> CreateDbParameter()
        {
            List<DbParameter> list = new List<DbParameter>();
            foreach (var item in this)
            {
                list.Add(item.CreateDbParameter(schem));
            }
            return list;
        }

        public string CreateInsertCoumText(bool isParameter)
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("(");
            int coumIndex = 0;
            foreach (var item in this)
            {
                if (coumIndex > 0) builder.Append(",");
                builder.Append(item.CreateInsertCoumText(isParameter));
                coumIndex++;
            }
            builder.Append(")");
            return builder.ToString();
        }
        public string CreateUpdateCoumText()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("set ");
            int coumIndex = 0;
            foreach (var item in this)
            {
                if (coumIndex > 0) builder.Append(",");
                builder.Append(item.CreateUpdateCoumText());
                coumIndex++;
            }
            //builder.Append(")");
            return builder.ToString();
        }

        public string CreatePrimaryCoumText()
        {
            var coum = schem.GetPrimaryKey(type.GetTableName());
            var coumInfo = this.FirstOrDefault(x => x.CoumName.ToLower() == coum);
            if (coumInfo == null) throw new Exception("没有主键信息");
            return coumInfo.CreateWhereCoumText();
        }
    }
}
